<?php

namespace tmcore\services\pay;

use think\facade\Request;
use app\common\enum\PayEnum;
use tmcore\exceptions\PayException;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\V3\AddPayloadSignaturePlugin;
use Yansongda\Pay\Plugin\Wechat\V3\WechatPublicCertsPlugin;

class WechatPayService extends BasePay
{

    /**
     * 支付配置
     * @var array
     */
    protected array $config;

    /**
     * 支付实例
     * @var Pay|\Yansongda\Pay\Provider\Wechat
     */
    protected $app;

    /**
     * 初始化微信支付配置
     * @param array $config 支付配置
     * @param array $extend 扩展参数
     */
    public function __construct($config = [], $extend = [])
    {
        if($config) {
            $this->config = $config;
        } else {
            $this->config = $this->getConfig('wechat');
        }

        $pay_config = [
            'wechat' => [
                'default' => [
                    // 必填-商户号
                    'mch_id' => $this->config['mch_id'],
                    // 选填-v2商户私钥
                    'mch_secret_key_v2' => $this->config['mch_secret_key_v2'],
                    // 必填-v3 商户秘钥
                    'mch_secret_key' => $this->config['mch_secret_key'],
                    // 必填-商户私钥 字符串或路径 apiclient_key.pem
                    'mch_secret_cert' => $this->config['mch_secret_cert'],
                    // 必填-商户公钥证书路径 apiclient_cert.pem
                    'mch_public_cert_path' => $this->config['mch_public_cert_path'],
                    // 必填
                    'notify_url' => $extend['notify_url'] ?? Request::domain() . '/http/wechatPay/notify',
                    // 选填-公众号 的 app_id
                    'mp_app_id' => $extend['mp_app_id'] ?? '',
                    // 选填-小程序 的 app_id
                    'mini_app_id' => $extend['mini_app_id'] ?? '',
                    // 选填-app 的 app_id
                    'app_id' => '',
                    // 选填-合单 app_id
                    'combine_app_id' => '',
                    // 选填-合单商户号
                    'combine_mch_id' => '',
                    // 选填-服务商模式下，子公众号 的 app_id
                    'sub_mp_app_id' => '',
                    // 选填-服务商模式下，子 app 的 app_id
                    'sub_app_id' => '',
                    // 选填-服务商模式下，子小程序 的 app_id
                    'sub_mini_app_id' => '',
                    // 选填-服务商模式下，子商户id
                    'sub_mch_id' => '',
                    // 选填-微信平台公钥证书路径, optional，强烈建议 php-fpm 模式下配置此参数
                    'wechat_public_cert_path' => [
                    ],
                    // 选填-默认为正常模式。可选为： MODE_NORMAL, MODE_SERVICE
                    'mode' => Pay::MODE_NORMAL,
                ]
            ],
            'logger' => [ // optional
                'enable' => false,
                'file' => './runtime/pay/wechat.log',
                'level' => 'info', // 建议生产环境等级调整为 info，开发环境为 debug
                'type' => 'single', // optional, 可选 daily.
                'max_file' => 30, // optional, 当 type 为 daily 时有效，默认 30 天
            ],
            'http' => [ // optional
                'timeout' => 5.0,
                'connect_timeout' => 5.0,
            ],
        ];

        $this->app = Pay::wechat($pay_config);
    }

    /**
     * 获取支付平台证书号(v3)
     * @return string
     */
    public function getPaySerialNo() {
        $data = $this->app->pay(
            [WechatPublicCertsPlugin::class, AddPayloadSignaturePlugin::class],
            []
        )->get('data', []);

        return $data[0]['serial_no'] ?? '';
    }

    /**
     * 微信支付统一下单
     * @param string $pay_for ['order_no' => '订单号', 'order_amount' => '订单金额', 'subject' => '订单信息']
     * @param array $params
     * @return array
     * @throws PayException
     */
    public function pay($pay_for, $params)
    {
        try {
            switch ($pay_for) {
                case 'wx_mp':
                    $result = $this->mp($params);
                    break;
                case 'wx_h5':
                    $result = $this->h5($params);
                    break;
                case 'wx_scan':
                    $result = $this->scan($params);
                    break;
                case 'wx_applet':
                    $result = $this->mini($params);
                    break;
                case 'wx_pos':
                    $result = $this->pos($params);
                    break;
                default:
                    throw new \Exception('支付方式错误');
            }

            $this->checkResultFail($result);

            return [
                'data' => $result,
                'pay_type' => PayEnum::WECHAT_PAY,
                'pay_for' => $pay_for
            ];
        } catch (\Exception $e) {
            throw new PayException($e->getMessage());
        }
    }

    /**
     * 公众号JS支付
     * @param array $params
     * @return array appId-appId nonceStr-随机数 package-参数 signType-签名方式 paySign-签名
     */
    public function mp($params)
    {
        return $this->app->mp([
            'out_trade_no' => $params['order_no'],
            'description' => $params['subject'],
            'amount' => [
                'total' => $params['order_amount'],
            ],
            'payer' => [
                'openid' => $params['openid'],
            ],
            'attach' => $params['attach'] ?? ''
        ])->toArray();
    }

    /**
     * H5支付
     * @param array $params
     * @return array h5_url-跳转url
     */
    public function h5($params)
    {
        return $this->app->h5([
            'out_trade_no' => $params['order_no'],
            'description' => $params['subject'],
            'amount' => [
                'total' => $params['order_amount'],
            ],
            'scene_info' => [
                'h5_info' => [
                    'type' => 'Wap',
                ]
            ],
            'attach' => $params['attach'] ?? ''
        ])->toArray();
    }

    /**
     * Native扫码支付
     * @param array $params
     * @return array code_url-扫码url
     */
    public function scan($params)
    {
        return $this->app->scan([
            'out_trade_no' => $params['order_no'],
            'description' => $params['subject'],
            'amount' => [
                'total' => $params['order_amount'],
            ],
            'attach' => $params['attach'] ?? ''
        ])->toArray();
    }

    /**
     * 小程序支付
     * @param array $params
     * @return array appId-appId nonceStr-随机数 package-参数 signType-签名方式 paySign-签名
     */
    public function mini($params)
    {
        return $this->app->mini([
            'out_trade_no' => $params['order_no'],
            'description' => $params['subject'],
            'amount' => [
                'total' => $params['order_amount'],
            ],
            'payer' => [
                'openid' => $params['openid'],
            ],
            'attach' => $params['attach'] ?? ''
        ])->toArray();
    }

    /**
     * 付款码支付（v2）
     * @param array $params
     * @return array
     */
    public function pos($params)
    {
        return $this->app->pos([
            'out_trade_no' => $params['order_no'],
            'body' => $params['subject'],
            'total_fee' => $params['order_amount'],
            'auth_code' => $params['auth_code'],
            'attach' => $params['attach'] ?? ''
        ])->toArray();
    }

    /**
     * 退款
     * @param array $params
     * @return array
     */
    public function refund($params)
    {
        return $this->app->refund([
            'out_trade_no' => $params['order_no'],
            'out_refund_no' => $params['refund_no'],
            'amount' => [
                'refund' => $params['refund_amount'],
                'total' => $params['order_amount'],
                'currency' => 'CNY',
            ],
            'reason' => isset($params['refund_reason'])?$params['refund_reason']:'',
            'notify_url' => Request::domain() . '/http/wechatPay/refund'
        ])->toArray();
    }

    /**
     * 查询订单
     * @param string $order_no
     * @return array
     */
    public function queryOrder($order_no)
    {
        if(!$order_no) return [];
        return $this->app->query(['out_trade_no' => $order_no])->toArray();
    }

    /**
     * 查询退款
     * @param string $order_no 订单号
     * @return array
     */
    public function queryRefund($order_no)
    {
        if(!$order_no) return [];
        return $this->app->query(['transaction_id' => $order_no, '_type' => 'refund'])->toArray();
    }

    /**
     * 关闭订单
     * @param string $order_no 订单号
     * @return array
     */
    public function close($order_no)
    {
        return $this->app->close(['out_trade_no' => $order_no])->toArray();
    }

    /**
     * 支付回调
     * @param null $params
     * @return mixed
     */
    public function notify($params = null)
    {
        return $this->app->callback($params);
    }

    /**
     * @notes 确认回调
     * @return ResponseInterface
     */
    public function notifySuccess()
    {
        return $this->app->success();
    }

    /**
     * 捕获错误
     * @param array $result
     * @throws \Exception
     */
    private function checkResultFail($result)
    {
        if (!empty($result['code'])) {
            throw new \Exception('微信支付:'. $result['code'] . '-' . $result['message']);
        }
    }
}